package com.study.crypto.basic.gb.service;

import com.alibaba.fastjson.JSON;
import com.study.crypto.basic.dto.RequestRandomDto;
import com.study.crypto.basic.dto.ResponseRandomDto;
import com.study.crypto.basic.dto.TaskIdDto;
import com.study.crypto.basic.dto.gb.*;
import com.study.crypto.basic.dto.gb.RequestReceiptDto.RequestReceiptDtoData;
import com.study.crypto.basic.dto.gb.RequestSealRecordDto.RequestSealRecordDtoData;
import com.study.crypto.basic.dto.gb.RequestUniqueCodeDto.RequestUniqueCodeDtoData;
import com.study.crypto.basic.dto.gb.ResponsePoliceDto.ResponsePoliceDtoData;
import com.study.crypto.basic.dto.gb.ResponsePoliceDto.ResponsePoliceDtoDataPackage;
import com.study.crypto.basic.dto.gb.ResponseUniqueCodeDto.ResponseUniqueCodeDtoData;
import com.study.crypto.basic.gb.bean.GADataBean;
import com.study.crypto.basic.gb.config.CustomProperties;
import com.study.crypto.basic.gb.dto.ReturnResult;
import com.study.crypto.basic.gb.entity.SealApply;
import com.study.crypto.basic.gb.entity.SealData;
import com.study.crypto.basic.gb.entity.UniqueCode;
import com.study.crypto.basic.gb.mapper.*;
import com.study.crypto.basic.gb.util.EncFileUtil;
import com.study.crypto.basic.gb.util.SymKeyEncUtil;
import com.study.crypto.basic.utils.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;

/**
 * @author Songjin
 * @since 2021-06-05 22:41
 */
@Transactional(rollbackFor = Exception.class)
@Service
public class ProductionService {
    
    @Autowired
    private CustomProperties properties;
    @Autowired
    private KeyStorageMapper keyStorageMapper;
    @Autowired
    private CertificationMapper certificationMapper;
    @Autowired
    private UniqueCodeMapper uniqueCodeMapper;
    @Autowired
    private SealDataMapper sealDataMapper;
    @Autowired
    private SealApplyMapper sealApplyMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 确认回执
     * @param request 请求
     * @return 响应
     */
    public ReturnResult<ResponseReceiptDto> confirmReceipt(RequestReceiptDto request) {
        RequestReceiptDtoData receiptDtoData = request.getData().get(0);
        String taskId = receiptDtoData.getTaskId();
        String deviceCode = receiptDtoData.getDeviceCode();
        String encDeviceCode = receiptDtoData.getEncDeviceCode();
        String yzbm = receiptDtoData.getYzbm();
        SealApply sealApply = sealApplyMapper.selectOne(SealApply.builder().taskId(taskId).build());
        if (sealApply == null) {
            ResponseReceiptDto dto = new ResponseReceiptDto(request.getTaskTypeCode());
            dto.setResultCode(GbConstants.FAIL_INNER);
            dto.setResultCodeMsg("不存在待处理的确认回执信息");
            return new ReturnResult<>(901018, "不存在待处理的确认回执信息", dto);
        }
        ResponsePoliceDtoData dtoData = new ResponsePoliceDtoData();
        dtoData.setYzbm(yzbm);
        dtoData.setErrorCode(GbConstants.SUCCESS_INNER);
        SealData sealData = sealDataMapper.selectByPrimaryKey(sealApply.getInfoId());
        boolean equals1 = deviceCode.equals(sealData.getDeviceCode());
        if (!equals1) {
            dtoData.setErrorCode("270504");
        }
        boolean equals2 = encDeviceCode.equals(sealData.getEncDeviceCode());
        if (!equals2) {
            dtoData.setErrorCode("270502");
        }
        boolean equals3 = yzbm.equals(sealData.getYzbm());
        if (!equals3) {
            dtoData.setErrorCode("270503");
        }
        ResponseReceiptDto dto = new ResponseReceiptDto(request.getTaskTypeCode());
        dto.setResultCode(GbConstants.SUCCESS_INNER);
        dto.setResultCodeMsg("备案回执信息已受理");
        dto.setData(Collections.singletonList(dtoData));
        return new ReturnResult<>(0, "操作成功", dto);
    }
    
    /**
     * 查询下载公安下发数据
     * @param request 请求
     * @return 响应
     */
    public ReturnResult<ResponsePoliceDto> checkResult(RequestPoliceDto request) {
        String taskId = request.getData().getTaskId();
        SealApply sealApply = sealApplyMapper.selectOne(SealApply.builder().taskId(taskId).build());
        if (sealApply == null) {
            ResponsePoliceDto dto = new ResponsePoliceDto(request.getTaskTypeCode());
            dto.setResultCode(GbConstants.FAIL_INNER);
            dto.setResultCodeMsg("查询信息不存在");
            return new ReturnResult<>(901043, "查询信息不存在", dto);
        }
        ResponsePoliceDto dto = new ResponsePoliceDto(sealApply.getTaskType());
        dto.setResultCode(GbConstants.SUCCESS_INNER);
        dto.setResultCodeMsg("查询成功");
        if (GbConstants.TASK_TYPE_CODE_REVOCATION_SEAL_INFO.equals(sealApply.getTaskType())) {
            ResponsePoliceDtoData dtoData = new ResponsePoliceDtoData();
            dtoData.setErrorCode(GbConstants.SUCCESS_INNER);
            dtoData.setYzbm(sealApply.getYzbm());
            dto.setData(Collections.singletonList(dtoData));
        } else {
            ResponsePoliceDtoDataPackage dataPackage = new ResponsePoliceDtoDataPackage();
            dataPackage.setYzbm(sealApply.getYzbm());
            dataPackage.setAppSymKeyEnc(sealApply.getAppSymKeyEnc());
            dataPackage.setDataSymKeyEnc(sealApply.getDataSymKeyEnc());
            dataPackage.setEncFile(sealApply.getEncFile());
            ResponsePoliceDtoData dtoData = new ResponsePoliceDtoData();
            dtoData.setErrorCode(GbConstants.SUCCESS_INNER);
            dtoData.setPackage_(dataPackage);
            dto.setData(Collections.singletonList(dtoData));
        }
        return new ReturnResult<>(0, "操作成功", dto);
    }
    
    /**
     * 上传备案信息
     * @param request 请求
     * @return 响应
     */
    public ReturnResult<ResponseSealRecordDto> uploadRecordInfos(RequestSealRecordDto request) throws GeneralSecurityException, IOException, InvalidCipherTextException {
        RequestSealRecordDtoData sealRecordDtoData = request.getData().get(0);
        String sealName = sealRecordDtoData.getSealData().getYzmc();
        String sealNum = sealRecordDtoData.getSealData().getYzbm();
    
        // 唯一赋码长度固定 8 位，加上行政区划 6 位即印章编号
        if (sealNum.length() != 14) {
            ResponseSealRecordDto dto = new ResponseSealRecordDto();
            dto.setResultCode(GbConstants.FAIL_INNER);
            dto.setResultCodeMsg("唯一赋码长度错误");
            return new ReturnResult<>(901045, "唯一赋码长度错误", dto);
        }
        
        // 判断区划+唯一赋码是否能查询到，查不到返回错误
        String areaNumber = sealRecordDtoData.getDistrictCode().substring(0, 3);
        String uniqueCodeNumber = sealNum.substring(6);
        UniqueCode uniqueCode = UniqueCode.builder().build();
        uniqueCode.setAreaNumber(areaNumber);
        uniqueCode.setUniqueCode(uniqueCodeNumber);
        UniqueCode uniqueCodeQuery = uniqueCodeMapper.selectOne(uniqueCode);
        if (uniqueCodeQuery == null) {
            ResponseSealRecordDto dto = new ResponseSealRecordDto();
            dto.setResultCode(GbConstants.FAIL_INNER);
            dto.setResultCodeMsg("唯一赋码不存在");
            return new ReturnResult<>(901032, "唯一赋码不存在", dto);
        }
    
        SealData selectOne1 = SealData.builder().yzmc(sealName).status(SealData.STATUS_FINISHED).build();
        SealData sealData1  = sealDataMapper.selectOne(selectOne1);
        if (sealData1 != null) {
            ResponseSealRecordDto dto = new ResponseSealRecordDto();
            dto.setResultCode(GbConstants.FAIL_INNER);
            dto.setResultCodeMsg("存在已经备案完成的印章名称是：" + sealName);
            return new ReturnResult<>(901027, "印章名称备案已经完成", dto);
        }
        
        selectOne1.setStatus(SealData.STATUS_SUBMITTED);
        SealData sealData2 = sealDataMapper.selectOne(selectOne1);
        if (sealData2 != null) {
            ResponseSealRecordDto dto = new ResponseSealRecordDto();
            dto.setResultCode(GbConstants.FAIL_INNER);
            dto.setResultCodeMsg("存在已经提交的备案申请，印章编码：" + sealNum + "-印章名称：" + sealName);
            return new ReturnResult<>(901013, "印章备案重复申请", dto);
        }
    
        SealData selectOne = SealData.builder().yzbm(sealNum).build();
        SealData sealData = sealDataMapper.selectOne(selectOne);
        if (sealData != null) {
            int status = sealData.getStatus();
            if (status == SealData.STATUS_FINISHED) {
                ResponseSealRecordDto dto = new ResponseSealRecordDto();
                dto.setResultCode(GbConstants.FAIL_INNER);
                dto.setResultCodeMsg("存在已经备案完成的印章编码是：" + sealNum);
                return new ReturnResult<>(901026, "印章编码备案已经完成", dto);
            }
            if (status == SealData.STATUS_SUBMITTED) {
                ResponseSealRecordDto dto = new ResponseSealRecordDto();
                dto.setResultCode(GbConstants.FAIL_INNER);
                dto.setResultCodeMsg("存在已经提交的备案申请，印章编码：" + sealNum + "-印章名称：" + sealName);
                return new ReturnResult<>(901013, "印章备案重复申请", dto);
            }
        }
    
        // 生成公安下发数据
        GADataBean gaDataBean = this.generatePoliceData(sealRecordDtoData);
    
        // 数据入库: bo_seal_data、bo_seal_apply
        SealApply sealApply = this.storeSealApply(sealRecordDtoData, gaDataBean);
    
        /*
         * 1.取出 token，并根据 token 从缓存 redis 中取出证书摘要
         * 2.然后以 taskId 为键，证书摘要为值继续在 redis 中存储一个不过期的键值对
         * 3.该键值对用作下载公安下发数据时的验签
         */
        String tokenInfo = request.getTokenInfo();
        String digest = redisTemplate.opsForValue().get(tokenInfo);
        if (StringUtils.isNotBlank(digest)) {
            redisTemplate.opsForValue().set(sealApply.getTaskId(), digest);
        }
        
        ResponseSealRecordDto dto = new ResponseSealRecordDto();
        dto.setResultCode(GbConstants.SUCCESS_INNER);
        dto.setResultCodeMsg("申请备案提交成功");
        dto.setData(new TaskIdDto(sealApply.getTaskId()));
        return new ReturnResult<>(0, "操作成功", dto);
    }
    
    /**
     * 存储申请数据到数据库
     * @param sealRecordDto
     * @param gaDataBean
     * @return 申请信息
     */
    private SealApply storeSealApply(RequestSealRecordDtoData sealRecordDto, GADataBean gaDataBean) {
        String sealNum = sealRecordDto.getSealData().getYzbm();
        SealDataDto sealDataDto = sealRecordDto.getSealData();
        LocalDateTime now = LocalDateTime.now();
        String sealDataText = JSON.toJSONString(sealDataDto);
        SealData sealDataForInsert = JSON.parseObject(sealDataText, SealData.class);
        sealDataForInsert.setId(EssPdfUtil.genRandomUuid());
        sealDataForInsert.setStatus(SealData.STATUS_SUBMITTED);
        sealDataForInsert.setCreateTime(now);
        sealDataForInsert.setUpdateTime(now);
        sealDataForInsert.setDistrictCode(sealRecordDto.getDistrictCode());
        sealDataForInsert.setYmlx("png");
        sealDataForInsert.setDeviceCode(gaDataBean.getDeviceCode());
        sealDataForInsert.setEncDeviceCode(gaDataBean.getEncDeviceCode());
        sealDataMapper.insert(sealDataForInsert);
        SealApply sealApply = SealApply.builder()
                                       .id(EssPdfUtil.genRandomUuid())
                                       .createTime(now)
                                       .updateTime(now)
                                       .sealSignCert(sealRecordDto.getSealSignCert())
                                       .sealEncCert(sealRecordDto.getSealEncCert())
                                       .districtCode(sealRecordDto.getDistrictCode())
                                       .taskId(EssPdfUtil.genRandomUuid())
                                       .infoId(sealDataForInsert.getId())
                                       .appSymKeyEnc(gaDataBean.getAppSymKeyEnc())
                                       .dataSymKeyEnc(gaDataBean.getDataSymKeyEnc())
                                       .encFile(gaDataBean.getEncFile())
                                       .taskType(GbConstants.TASK_TYPE_CODE_UPLOAD_RECORD_INFOS)
                                       .yzbm(sealNum).build();
        sealApplyMapper.insert(sealApply);
        return sealApply;
    }
    
    /**
     * 生成公安下发数据
     * @param sealRecord
     * @return 公安下发数据
     */
    private GADataBean generatePoliceData(RequestSealRecordDtoData sealRecord) throws GeneralSecurityException, IOException, InvalidCipherTextException {
        // 产生设备编号、加密设备编号，产生对称密钥
        String maxDeviceCode_ = sealDataMapper.selectMaxDeviceCode();
        long deviceCodeVal;
        if (StringUtils.isNotBlank(maxDeviceCode_)) {
            byte[] decode = Hex.decode(maxDeviceCode_);
            deviceCodeVal = ByteUtility.asLong(decode) + 1;
        } else {
            deviceCodeVal = properties.getDefaultDeviceCode() + 1;
        }
        String deviceCode = Hex.toHexString(ByteUtility.asBytes(deviceCodeVal)).toUpperCase();
        byte[] dataSymKey = SM4Utils.generateKey();
        byte[] appSymKey = SM4Utils.generateKey();
        byte[] encDeviceCodeBytes = SM4Utils.encrypt_ecb_padding(dataSymKey, deviceCode.getBytes());
        String encDeviceCode = Base64.encodeBase64String(encDeviceCodeBytes);
    
        // 生成应用维护对称密钥、数据加密对称密钥
        SealDataDto sealDataDto = sealRecord.getSealData();
        String encCert = sealRecord.getSealEncCert();
        byte[] publicKeyBytes = KeyUtils.obtainPublicKeyBytes(Base64.decodeBase64(encCert));
        PublicKey publicKey = KeyUtils.convertPublicKey(publicKeyBytes);
        byte[] encrypt1 = SM2Utils.encrypt(publicKey, dataSymKey, SM2Engine.Mode.C1C3C2);
        byte[] encrypt2 = SM2Utils.encrypt(publicKey, appSymKey, SM2Engine.Mode.C1C3C2);
        String dataSymKeyEnc = SymKeyEncUtil.capsulateSymKeyEnc(encrypt1);
        String appSymKeyEnc = SymKeyEncUtil.capsulateSymKeyEnc(encrypt2);
        String encFile = EncFileUtil.capsulateEncFile(dataSymKey, deviceCode, sealDataDto);
        return GADataBean.builder()
                         .encDeviceCode(encDeviceCode)
                         .deviceCode(deviceCode)
                         .appSymKeyEnc(appSymKeyEnc)
                         .dataSymKeyEnc(dataSymKeyEnc)
                         .encFile(encFile).build();
    }
    
    /**
     * 申请唯一赋码
     * @param request 请求
     * @return 响应
     */
    public ReturnResult<ResponseUniqueCodeDto> applySealCode(RequestUniqueCodeDto request) {
        RequestUniqueCodeDtoData requestData = request.getData();
        String areaNumber = requestData.getAreaNumber();
        String maxUniqueCode = uniqueCodeMapper.selectMaxUniqueCode(areaNumber);
        String uniqueCode;
        if (StringUtils.isBlank(maxUniqueCode)) {
            uniqueCode = "00000001";
        } else {
            uniqueCode =  StringUtils.leftPad((NumberUtils.toInt(maxUniqueCode) + 1) + "", 8, "0");
        }
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime tomorrow = now.plusDays(1);
        UniqueCode record = UniqueCode.builder()
                                      .id(EssPdfUtil.genRandomUuid())
                                      .createTime(now)
                                      .updateTime(now)
                                      .areaNumber(areaNumber)
                                      .uniqueCode(uniqueCode)
                                      .deadline(tomorrow)
                                      .build();
        int insert = uniqueCodeMapper.insert(record);
        ResponseUniqueCodeDto response = new ResponseUniqueCodeDto();
        if (insert < 1) {
            response.setResultCode(GbConstants.UNIQUE_CODE_ERROR);
            response.setResultCodeMsg("生成唯一赋码记录出错");
            return new ReturnResult<>(901021, "申请唯一赋码异常", response);
        }
        response.setData(ResponseUniqueCodeDtoData.builder().sealCode(uniqueCode).build());
        response.setResultCode(GbConstants.SUCCESS_INNER);
        response.setResultCodeMsg("申请唯一赋码成功");
        return new ReturnResult<>(0, "操作成功", response);
    }
    
    /**
     * 申请随机数
     * @param request 请求
     * @return 响应
     */
    @Transactional(readOnly = true)
    public ReturnResult<ResponseRandomDto> applyRandom(RequestRandomDto request) {
        String applicantCert = request.getApplicantCert();
        String randomA = request.getRandomA();
        String randomB = EssPdfUtil.genRandomUuid().substring(0, 16);
        String digestHex = DigestUtil.sm3Base64(Base64.decodeBase64(applicantCert));
        String key = randomA.concat(randomB);
        // 使用 randomA+randomB 为键，证书摘要为值存入 redis，存活时长设置为 30s
        redisTemplate.opsForValue().set(key, digestHex, Duration.ofHours(5));
        ResponseRandomDto responseRandomDto = new ResponseRandomDto();
        responseRandomDto.setResultCodeMsg("随机数申请成功");
        responseRandomDto.setRandomB(randomB);
        return new ReturnResult<>(0, "操作成功", responseRandomDto);
    }
    
}
